
import traceback
import multiprocessing as mp
from S3D import XmlHelper, IO, PS
from S3D.Util import Workspace
from S3D.Tools.Sound.EventSearcher.SettingsDialog import *

EVENT_FIELD_PROPS = {
    EVENT_PROP_PROJECT:     "project",
    EVENT_PROP_PATH:        "path",
}

SOUNDBANK_TYPE = [ "Memory", "Autoloading", "Streaming" ]
SOUNDBANK_TYPE_MEMORY       = 0
SOUNDBANK_TYPE_AUTOLOADING  = 1
SOUNDBANK_TYPE_STREAMING    = 2
SOUNDBANK_TYPE_INVALID      = -1

PROP_CONTEXT_SHOW       = 0
PROP_CONTEXT_CSV        = 1
PROP_CONTEXT_TXT        = 2
PROP_CONTEXT_OTHER      = 3

LEVEL_PREFIXES = [
    ("level_",          ""),
    ("multiplayer_",    "mp"),
]

DEFAULT_RANGES = {}

def addDefRange(rangeFrom, rangeTo, rangeToEnable, *indices):
    for index in indices:
        DEFAULT_RANGES[index] = rangeFrom, rangeTo, rangeToEnable

addDefRange(0, -6,      True, 0)         # volume
addDefRange(2, 15,      True, 3)         # min distance
addDefRange(0, 9999,    True, 4)         # max distance
addDefRange(0, 0,       False, 1, 2, *range(5, len(Sound.EVENT_PROPERTIES))) # other parameters

def simplifyLevelName(name):
    for prefix, replace in LEVEL_PREFIXES:
        if name.startswith(prefix):
            return replace + name[len(prefix):]

    return name

class EventInfo:
    def __init__(self, project, name, path, definitions, params):
        self.project        = project
        self.name           = name
        self.path           = path
        self.definitions    = definitions
        self.params         = params
        self.banks          = []
        self.levels         = []

    def copy(self):
        return EventInfo(
            self.project,
            self.name,
            self.path,
            self.definitions,
            self.params)

    def getProp(self, index, context):
        if index in EVENT_FIELD_PROPS:
            return getattr(self, EVENT_FIELD_PROPS[index])

        if index == EVENT_PROP_SOUNDBANK:
            banks = [bank + ".fsb" for bank in self.banks]
            banks.sort()
            if context == PROP_CONTEXT_CSV:
                return '; '.join(banks)

            return ', '.join(banks)

        if index == EVENT_PROP_LEVELS:
            if context == PROP_CONTEXT_CSV:
                return '; '.join(self.levels)

            levels = self.levels
            if context == PROP_CONTEXT_SHOW:
                levels = list(map(simplifyLevelName, levels))

            res = ', '.join(levels)
            if not res:
                if context == PROP_CONTEXT_SHOW:
                    return "<font color='red'><b>&lt;Never used&gt;</b></font>"

                return "<Never used>"

            if context == PROP_CONTEXT_SHOW and len(res) > 100:
                return res[:100] + "..."

            return res

        if index >= EVENT_PROP_PARAM and index <= EVENT_PROP_PARAM_LAST:
            paramIndex = index - EVENT_PROP_PARAM

            if context == PROP_CONTEXT_CSV and paramIndex in Sound.EVENT_PROPERTIES_IN_DB:
                return "%.f dB" % self.params[paramIndex]

            return str(self.params[paramIndex])

        raise AssertionError("Unknown parameter: %s(%d)" % (EVENT_PROP_DESC[index], index))

class Updater(mp.Process):
    def __init__(self, workerQueue, workspace, projects, levelsCache):
        super(Updater, self).__init__()
        self.daemon = True

        self.recv_conn, self.send_conn = mp.Pipe(False)

        self.queue          = workerQueue
        self.workspace      = workspace
        self.projects       = projects
        self.levelsCache    = levelsCache

    def sendMessage(self, message, *data):
        if len(data) == 1:
            self.send_conn.send((message, data[0]))
        else:
            self.send_conn.send((message, data))

    def _setStatus(self, message):
        self.queue.put(("status", message))

    def _sendReply(self, message, *data):
        if len(data) == 1:
            self.queue.put((message, data[0]))
        else:
            self.queue.put((message, data))

    def run(self):
        Workspace.setWorkspace(self.workspace)

        while True:
            try:
                if self.recv_conn.poll(1):
                    self._processMessage()
                else:
                    self._checkProjects()
                    self._checkLsa()
            except:
                traceback.print_exc()

    def _processMessage(self):
        message, data = self.recv_conn.recv()
        if message == "workspace":
            self.workspace, self.projects, self.levelsCache = data
            Workspace.setWorkspace(self.workspace)
        else:
            print("Updater: Unknown message: %s" % message)

    def _checkLsa(self):
        if self.levelsCache is None:
            self.levelsCache = IO.ModTimeCache(Sound.getLsaPath(), ".lsa")

        if not self.levelsCache.isModified():
            return

        self._setStatus("Parsing lsa...")
        self.levelsCache.saveCurrentTime()
        levels = Sound.getLevelsStats()
        self._sendReply("levels", self.workspace, self.levelsCache, levels)
        self._setStatus("Ready")

    def _checkProjects(self):
        autoloadInfo = Sound.getAutoloadingBanksInfo()

        ready = False
        for path in Sound.getProjectsPathList():
            _, projectName = os.path.split(path)
            curTime = os.path.getmtime(path)

            try:
                _, prevTime = self.projects[projectName]
                if curTime == prevTime:
                    continue
            except KeyError:
                pass

            fdpInfo = self._processFdp(projectName, path, autoloadInfo)
            self.projects[projectName] = (path, curTime)

            print("Sending update: %s" % projectName)
            self._sendReply("update", self.workspace, projectName, path, curTime, fdpInfo)
            ready = True

        if ready:
            self._setStatus("Ready")

    def _processFdp(self, projectName, projectPath, autoloadInfo):
        print("Parsing %s" % projectPath)
        self._setStatus("Loading %s" % projectPath.lower())
        doc = XmlHelper.parseFile(projectPath)
        project = doc.getChild("project")

        events = []

        print("Processing...")

        # collect wavebanks
        waves = []
        waveIndex = {}
        soundBanks = {}

        for soundbank in project.getAllChilds("soundbank"):
            name = soundbank.getValue("name")
            sbTypes = []
            for index, platform in enumerate(Sound.FMOD_PLATFORMS):
                bankType = soundbank.getValue("%sbanktype" % platform)
                if bankType == "Stream":
                    sbTypes.append(SOUNDBANK_TYPE_STREAMING)
                elif bankType == "Sample":
                    if autoloadInfo.isAutoload(index, name):
                        sbTypes.append(SOUNDBANK_TYPE_AUTOLOADING)
                    else:
                        sbTypes.append(SOUNDBANK_TYPE_MEMORY)
                else:
                    sbTypes.append(SOUNDBANK_TYPE_INVALID)

            sbWaves = set()
            soundBanks[name] = sbTypes, sbWaves
            for waveform in soundbank.getAllChilds("waveform"):
                filename = waveform.getValue("filename")
                try:
                    index = waveIndex[filename]
                except KeyError:
                    index = len(waves)
                    waves.append(filename)
                    waveIndex[filename] = index

                sbWaves.add(index)

        # collect sound definitions
        soundDefinitions = {}
        for name, waves in walkAllSoundDefinitions(project):
            soundDefinitions[name] = [waveIndex[p] for p in waves]

        # collect events
        for eventInfo in walkAllEvents(project):
            events.append(EventInfo(projectName, *eventInfo))

        print("Done.")
        return events, soundDefinitions, soundBanks, waves

def walkAllSoundDefinitions(group):
    for soundDef in group.getAllChilds("sounddef"):
        name = soundDef.getValue("name")
        waves = []
        for waveform in soundDef.getAllChilds("waveform"):
            waves.append(waveform.getValue("filename"))

        yield name, waves

    for soundDefFolder in group.getAllChilds("sounddeffolder"):
        for p in walkAllSoundDefinitions(soundDefFolder):
            yield p

def walkAllEvents(group, path = ""):
    for event in group.getAllChilds("event"):
        name = event.getValue("name")

        params = [float(event.getValue(paramName)) for _, paramName in Sound.EVENT_PROPERTIES]

        definitions = []
        for layer in event.getAllChilds("layer"):
            for sound in layer.getAllChilds("sound"):
                definitions.append(sound.getValue("name"))

        yield name, os.path.join(path, name), definitions, params

    for eventGroup in group.getAllChilds("eventgroup"):
        name = eventGroup.getValue("name")
        for p in walkAllEvents(eventGroup, os.path.join(path, name)):
            yield p
